/***
 *底层控制箱控制指令
 *20250920
 ***/
#ifndef BOTTOMCONTROLBOX_H
#define BOTTOMCONTROLBOX_H

#include <QObject>
#include <QUdpSocket>
#include <QTimer>
#include <QtCore/qglobal.h>
#include <cstdint>
#include <QDataStream>

#if defined(BOTTOMCONTROLBOX_LIBRARY)
#  define BOTTOMCONTROLBOX_EXPORT Q_DECL_EXPORT
#else
#  define BOTTOMCONTROLBOX_EXPORT Q_DECL_IMPORT
#endif

//结构体定义

// 基础数据类型定义（确保跨平台字节长度一致）s
typedef uint8_t  uint8;   // 1字节无符号
typedef uint16_t uint16;  // 2字节无符号
typedef int16_t  int16;   // 2字节有符号

/**
 * @brief CRC16校验（CRC-16/Modbus协议）
 * @param data 待校验字节流
 * @param length 字节流长度
 * @return 16位CRC校验值（小端模式）
 */
inline uint16 crc16(const uint8* data, int length) {
    uint16 crc = 0xFFFF;  // 初始值
    const uint16 poly = 0xA001;  // 多项式
    for (int i = 0; i < length; ++i) {
        crc ^= static_cast<uint16>(data[i]);
        for (int j = 0; j < 8; ++j) {
            crc = (crc & 0x0001) ? ((crc >> 1) ^ poly) : (crc >> 1);
        }
    }
    return crc;
}

// 将小端uint32_t转换为float（通过共用体安全转换，避免指针强转的Undefined Behavior）
union FloatConverter {
    uint32_t u32;
    float f32;
};

/**
 * @brief 控制指令结构体（航控→底层，47字节）
 * 严格遵循协议表1：帧头(0xFE)→数据长度→...→校验码→帧尾(0xFF)
 */
#pragma pack(1)  // 取消字节对齐（避免结构体填充导致长度错误）
struct ControlCommand_BC {
    uint8  header;          // 1字节：帧头，固定0xFE
    uint8  reserve0;        // 1字节: 预留，固定0x0000
    uint16 dataLen;         // 2字节：数据长度=40（46-1-1-2-2，排除帧头/尾/校验/自身）
    uint8  srcNode;         // 1字节：源节点（如航控=0x01）
    uint8  destNode;        // 1字节：目标节点（如底层=0x02）
    uint8  cmdType;         // 1字节：指令类型（如控制指令=0x01）
    uint8  confirmFlag;     // 1字节：确认标志，固定0x01（需要确认）
    uint16 heartbeat;       // 2字节：心跳计数器（0-65535循环）
    uint16 reserve1;        // 2字节：预留，固定0x0000

    // 左柴油机/喷泵参数
    uint8  leftEngineStartStopSeq;  // 1字节：左柴油机启停指令序号（0-255循环）
    uint8  leftEngineStartStop;     // 1字节：左柴油机启停（0默认/1点火/2启动/3停机/4熄火）
    uint8  leftPumpGearSeq;         // 1字节：左喷泵挡位指令序号
    uint8  leftPumpGear;            // 1字节：左喷泵挡位（0空挡/1前进/2倒挡）
    uint8  leftReserve3;            // 1字节：左预留3
    uint8  leftPumpThrottleSeq;     // 1字节：左喷泵油门指令序号
    uint16 leftPumpThrottle;        // 2字节：左喷泵油门（0-1000‰，小端）
    uint8  leftReserve4;            // 1字节：左预留4
    uint8  leftPumpRudderAngleSeq;  // 1字节：左喷泵舵角指令序号
    int16  leftPumpRudderAngle;     // 2字节：左喷泵舵角（-300~300，0.1°单位，小端）

    // 右柴油机/喷泵参数
    uint8  rightEngineStartStopSeq; // 1字节：右柴油机启停指令序号
    uint8  rightEngineStartStop;    // 1字节：右柴油机启停（同左）
    uint8  rightPumpGearSeq;        // 1字节：右喷泵挡位指令序号
    uint8  rightPumpGear;           // 1字节：右喷泵挡位（同左）
    uint8  rightReserve3;           // 1字节：右预留3
    uint8  rightPumpThrottleSeq;    // 1字节：右喷泵油门指令序号
    uint16 rightPumpThrottle;       // 2字节：右喷泵油门（0-1000‰，小端）
    uint8  rightReserve4;           // 1字节：右预留4
    uint8  rightPumpRudderAngleSeq; // 1字节：右喷泵舵角指令序号
    int16  rightPumpRudderAngle;    // 2字节：右喷泵舵角（同左）

    // 预留字段
    uint16 reserve2;        // 2字节：预留1
    uint16 reserve3;        // 2字节：预留2
    uint16 reserve4;        // 2字节：预留3
    uint16 reserve5;        // 2字节：预留4

    uint16 crc;             // 2字节：CRC16校验码（小端）
    uint8 reserve6;         // 1字节：预留5
    uint8  tail;            // 1字节：帧尾，固定0xFF
};

/**
 * @brief 回令结构体（底层→航控，43字节）
 * 严格遵循协议表2：帧头(0xFE)→数据长度→...→校验码→帧尾(0xFF)
 */
struct ResponseCommand {
    uint8  header;          // 1字节：帧头，固定0xFE
    uint8  reserve0;        // 1字节：预留，固定0x0000
    uint16 dataLen;         // 2字节：数据长度=36（42-1-1-2-2，排除帧头/尾/校验/自身）
    uint8  srcNode;         // 1字节：源节点（如底层=0x02）
    uint8  destNode;        // 1字节：目标节点（如航控=0x01）
    uint8  cmdType;         // 1字节：指令类型（与控制指令一致）
    uint8  confirmFlag;     // 1字节：确认标志，固定0x03（确认帧）
    uint16 heartbeat;       // 2字节：心跳计数器（与控制指令一致）
    uint16 reserve1;        // 2字节：预留，固定0x0000

    // 左柴油机/喷泵回令
    uint8  leftEngineStartStopSeq;  // 1字节：左柴油机启停指令序号（与控制指令一致）
    uint8  leftEngineStartStopResp; // 1字节：左柴油机启停回令（0不涉及/1成功/2失败/3非法）
    uint8  leftPumpGearSeq;         // 1字节：左喷泵挡位指令序号
    uint8  leftPumpGearResp;        // 1字节：左喷泵挡位回令（同启停回令）
    uint8  leftReserve3;            // 1字节：左预留3
    uint8  leftPumpThrottleSeq;     // 1字节：左喷泵油门指令序号
    uint8  leftPumpThrottleResp;    // 1字节：左喷泵油门回令
    uint8  leftReserve4;            // 1字节：左预留4
    uint8  leftPumpRudderAngleSeq;  // 1字节：左喷泵舵角指令序号
    uint8  leftPumpRudderAngleResp; // 1字节：左喷泵舵角回令

    // 右柴油机/喷泵回令
    uint8  rightEngineStartStopSeq; // 1字节：右柴油机启停指令序号
    uint8  rightEngineStartStopResp;// 1字节：右柴油机启停回令
    uint8  rightPumpGearSeq;        // 1字节：右喷泵挡位指令序号
    uint8  rightPumpGearResp;       // 1字节：右喷泵挡位回令
    uint8  rightReserve3;           // 1字节：右预留3
    uint8  rightPumpThrottleSeq;    // 1字节：右喷泵油门指令序号
    uint8  rightPumpThrottleResp;   // 1字节：右喷泵油门回令
    uint8  rightReserve4;           // 1字节：右预留4
    uint8  rightPumpRudderAngleSeq; // 1字节：右喷泵舵角指令序号
    uint8  rightPumpRudderAngleResp;// 1字节：右喷泵舵角回令

    // 预留字段
    uint16 reserve2;        // 2字节：预留1
    uint16 reserve3;        // 2字节：预留2
    uint16 reserve4;        // 2字节：预留3
    uint16 reserve5;        // 2字节：预留4

    uint16 crc;             // 2字节：CRC16校验码（小端）
    uint8 reserve6;         // 2字节：预留5
    uint8  tail;            // 1字节：帧尾，固定0xFF
};

/**
 * @brief 87字节反馈数据结构体（对应协议表2：底层控制箱反馈数据）
 * 严格遵循协议表2：帧头(0xFE)→数据长度→...→校验码→帧尾(0xFF)
 */
typedef struct {
    // 0-1字节：帧头+预留
    quint8 frameHead;          // 0字节：帧头（0xFE）
    quint8 reserve1;           // 1字节：预留

    // 2-3字节：数据长度（小端）- 协议定义：除去帧头/帧尾/校验码/数据长度后的字节数
    quint16 dataLen;           // 2-3字节：数据长度（小端字节序）

    // 4-7字节：节点信息+指令类型+确认标志
    quint8 srcNode;            // 4字节：源节点编号（02）
    quint8 dstNode;            // 5字节：目标节点编号（01）
    quint8 cmdType;            // 6字节：指令类型（01）
    quint8 confirmFlag;        // 7字节：确认标志（00）

    // 8-9字节：心跳（小端）
    quint16 heartbeat;         // 8-9字节：心跳序号（0-65535，小端）

    // 10-11字节：当前控制模式（小端）
    quint16 controlMode;       // 10-11字节：控制模式（0=急停，1=手动，2=自动，3=停止，4=调试）

    // 12-15字节：系统反馈1-4（bits）
    quint8 sysFeedback1;       // 12字节：系统反馈1
    quint8 sysFeedback2;       // 13字节：系统反馈2
    quint8 sysFeedback3;       // 14字节：系统反馈3
    quint8 sysFeedback4;       // 15字节：系统反馈4

    // 16-19字节：油箱油量（小端，单位：1‰）
    quint16 oilTank1;          // 16-17字节：油箱1油量（0-1000‰）
    quint16 oilTank2;          // 18-19字节：油箱2油量（0-1000‰，无则为0）

    // 20-23字节：舵角值（小端，单位：0.1°，int类型）
    qint16 leftRudderAngle;    // 20-21字节：左舵角（-350~350，正为右舵）
    qint16 rightRudderAngle;   // 22-23字节：右舵角（-350~350，正为右舵）

    // 24-31字节：系统预留1-4（小端）
    quint16 sysReserve1;       // 24-25字节：预留1
    quint16 sysReserve2;       // 26-27字节：预留2
    quint16 sysReserve3;       // 28-29字节：预留3
    quint16 sysReserve4;       // 30-31字节：预留4

    // 32-57字节：左柴油机数据
    quint16 leftDieselGear;    // 32-33字节：左柴油机挡位（0=空挡，1=前进，2=倒挡）
    quint16 leftDieselRpm;     // 34-35字节：左柴油机转速（0.125rpm，0-30000）
    quint16 leftDieselThrottle;// 36-37字节：左柴油机油门（0.40%，0-1000）
    float  leftDieselRunTime;  // 38-41字节：左柴油机总运行时间（0.05hr）
    quint16 leftDieselFuelRate;// 42-43字节：左柴油机耗油率（0.05L/h，0-30000）
    quint8  leftDieselCoolTemp;// 44字节：左柴油机冷却液温度（1°C，实际值-40）
    quint8  leftDieselFuelTemp;// 45字节：左柴油机燃料温度（1°C，实际值-40）
    float  leftDieselFaultCode;// 46-49字节：左柴油机故障码
    quint8  leftDieselOilPress;// 50字节：左柴油机机油压力（4kPa，0-255）
    quint8  leftDieselIntakeTemp;// 51字节：左柴油机进气温度（1°C，实际值-40）
    quint16 leftDieselIgnVolt; // 52-53字节：左柴油机点火电压（0.05V，0-10000）
    quint8  leftDieselStartStop;// 54字节：左柴油机启停状态（1=点火，2=启动，3=停机，4=熄火）
    quint8  leftDieselRes1;    // 55字节：左柴油机预留1
    quint8  leftDieselRes2;    // 56字节：左柴油机预留2
    quint8  leftDieselRes3;    // 57字节：左柴油机预留3

    // 58-83字节：右柴油机数据
    quint16 rightDieselGear;   // 58-59字节：右柴油机挡位（0=空挡，1=前进，2=倒挡）
    quint16 rightDieselRpm;    // 60-61字节：右柴油机转速（0.125rpm，0-30000）
    quint16 rightDieselThrottle;// 62-63字节：右柴油机油门（0.40%，0-1000）
    float  rightDieselRunTime; // 64-67字节：右柴油机总运行时间（0.05hr）
    quint16 rightDieselFuelRate;// 68-69字节：右柴油机耗油率（0.05L/h，0-30000）
    quint8  rightDieselCoolTemp;// 70字节：右柴油机冷却液温度（1°C，实际值-40）
    quint8  rightDieselFuelTemp;// 71字节：右柴油机燃料温度（1°C，实际值-40）
    quint32 rightDieselFaultCode;// 72-75字节：右柴油机故障码（协议为Unsigned int，4字节）
    quint8  rightDieselOilPress;// 76字节：右柴油机机油压力（4kPa，0-255）
    quint8  rightDieselIntakeTemp;// 77字节：右柴油机进气温度（1°C，实际值-40）
    quint16 rightDieselIgnVolt; // 78-79字节：右柴油机点火电压（0.05V，0-10000）
    quint8  rightDieselStartStop;// 80字节：右柴油机启停状态（1=点火，2=启动，3=停机，4=熄火）
    quint8  rightDieselRes1;   // 81字节：右柴油机预留1
    quint8  rightDieselRes2;   // 82字节：右柴油机预留2
    quint8  rightDieselRes3;   // 83字节：右柴油机预留3

    // 84-87字节：校验码+预留+帧尾
    quint16 crcCode;           // 84-85字节：16位CRC校验码（小端）
    quint8  reserve2;          // 86字节：预留
    quint8  frameTail;         // 87字节：帧尾（0xFF）
} FeedbackDataStruct, *PFeedbackDataStruct;

//心跳协议结构(15字节)
struct HeartbeatFrame {
    unsigned char frameHeader;      // 帧头 0xFE
    unsigned char reserved1;        // 预留
    unsigned short dataLength;      // 数据长度
    unsigned char sourceNode;       // 源节点编号 0x01
    unsigned char destNode;         // 目的节点编号 0xBB
    unsigned char cmdType;          // 指令类型 0xAC
    unsigned char ackFlag;          // 确认标志 0x0
    unsigned short sequence;        // 序号
    unsigned short reserved2;       // 预留字节 0x0000
    unsigned short crcCheck;        // 校验码
    unsigned char frameTail;        // 帧尾 0xFF
};

#pragma pack()

// **********枚举所有控制指令类型（用于区分不同指令的超时）**********
enum class CmdType {
    LeftDieselStartStop,    // 左柴油机启停
    LeftPumpGear,           // 左喷泵挡位
    LeftPumpThrottle,       // 左喷泵油门
    LeftPumpRudderAngle,    // 左喷泵舵角
    RightDieselStartStop,   // 右柴油机启停
    RightPumpGear,          // 右喷泵挡位
    RightPumpThrottle,      // 右喷泵油门
    RightPumpRudderAngle    // 右喷泵舵角
};

inline uint qHash(const CmdType &key, uint seed = 0)
{
    // 将枚举值转换为 uint 后再哈希
    return qHash(static_cast<uint>(key), seed);
}

// **********指令唯一标识（指令类型+序号）**********
using CmdKey = QPair<CmdType, quint8>;  // 键：<指令类型, 指令序号>

class BOTTOMCONTROLBOX_EXPORT BottomControlBox : public QObject
{
    Q_OBJECT

public:
    explicit BottomControlBox(QObject *parent = nullptr);
    ~BottomControlBox();

    //对外接口（发送），等待3s，如果没有收到正确回令，则返回false
    bool leftDieselEngineStartStop(uint8 setCmd);  //左柴油机启停设置
    bool leftSprayPumpGear(uint8 setCmd);          //左喷泵挡位设置
    bool leftSprayPumpThrottle(uint16 setCmd);     //左喷泵油门设置
    bool leftSprayPumpRudderAngle(int setCmd);     //左喷泵舵角设置
    bool rightDieselEngineStartStop(uint8 setCmd); //右柴油机启停设置
    bool rightSprayPumpGear(uint8 setCmd);         //右喷泵挡位设置
    bool rightSprayPumpThrottle(uint16 setCmd);    //右喷泵油门设置
    bool rightSprayPumpRudderAngle(int setCmd);    //右喷泵舵角设置

    void init();            //初始化整体
    void setNetConfig(QString localIp, quint16 localPort,
                      QString dstIp, quint16 dstPort);    //设置ip和端口
    void setNode(quint8 srcNode, quint8 dstNode); //设置节点

    void startSending();    // 开始定时发送心跳
    void stopSending();     // 停止定时发送心跳
    void showHeartDebug(bool isShow);   //是否显示心跳发送和接收信息

    FeedbackDataStruct getFeedbackData()const;  //获取反馈数据

signals:
    void sigError(const QString& msg);  //错误通知
    void sigSuccess(const QString& msg);  //成功通知
    void sigFeedbackParsed(FeedbackDataStruct); //发送接收反馈的内容

private:
    QUdpSocket* m_udpSocket;  // UDP通讯对象
    uint16 m_heartbeat;       // 心跳计数器（0-65535循环）
    uint8  m_cmdSeq;          // 指令序号（0-255循环，255后跳1）

    ControlCommand_BC m_sendCmd; //发送指令结构体
    ResponseCommand m_reply;       //处理回令结构体

    FeedbackDataStruct feedbackData;  //反馈数据结构体

    QTimer *m_timer;          // 定时器
    unsigned short m_sequence; // 序号计数器
    HeartbeatFrame m_frame;   // 帧结构

    // **********超时检查定时器（定期扫描超时）**********
    QTimer *m_timeoutTimer = nullptr;
    // **********超时时间常量（3秒）**********
    const int TIMEOUT_MS = 3000;
    // **********待确认指令列表（键：指令标识，值：发送时间）**********
    QHash<CmdKey, QTime> m_pendingCmds;

    bool m_isShowHeart;  //是否显示心跳信息

    QString m_localIp = ""; //本地ip
    quint16 m_localPort = 0;  //本地端口
    QString m_dstIp = "";   //目标IP
    quint16 m_dstPort = 0;  //目标端口

    quint8 m_srcNode;   //源节点
    quint8 m_dstNode;   //目标节点

private:
    void initSendCmd();       //初始化发送结构体函数
    bool sendCmd();           //发送m_sendCmd的内容到底层控制箱
    void processFeedbackInfo(const QByteArray& strs);   //处理反馈信息
    void processCommandReply(const QByteArray& strs);   //处理回令信息
    quint16 leToHost16(const quint8 *data); //小端字节数组转主机字节序（无符号16位）
    qint16 leToHost16S(const quint8 *data); //小端字节数组转主机字节序（有符号16位）
    QByteArray packFrame();    // 打包帧数据

public slots:
    void onReceive();       //接收消息处理
    void onSendHeartbeat(); //定时发送心跳包
    void onCheckTimeout();  //超时检测
};

#endif // BOTTOMCONTROLBOX_H
